/**
 *  Copyright 2002-2009 the original author or authors.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package foo.bar.blog.web.action.administration;

import foo.bar.blog.core.domain.Permission;
import foo.bar.blog.core.domain.Role;
import foo.bar.blog.core.domain.User;
import foo.bar.blog.core.security.PermissionType;
import foo.bar.blog.core.security.RoleType;
import foo.bar.blog.core.services.UserService;
import foo.bar.blog.web.action.ExtendedPaginatedList;
import foo.bar.blog.web.action.PaginatedBaseActionBean;
import foo.bar.blog.web.extension.UserTypeConverter;
import net.sourceforge.stripes.action.*;
import net.sourceforge.stripes.controller.LifecycleStage;
import net.sourceforge.stripes.integration.spring.SpringBean;
import net.sourceforge.stripes.validation.*;
import org.perf4j.aop.Profiled;

import java.util.*;

/**
 * User management related actions.
 *
 * @author tmjee
 * @version $Date: 2009-06-06 13:26:52 +0000 (Sat, 06 Jun 2009) $ $Id$
 */
public class UserManagementActionBean extends PaginatedBaseActionBean {

    public static final String VIEW = "administration/userManagement.jsp";
    public static final String VIEW_DETAILS = "administration/viewUserDetails.jsp";
    public static final String EDIT_DETAILS = "administration/editUserDetails.jsp";
    public static final String DELETE_DETAILS = "administration/userManagement_table.jsp";
    public static final String USER_LISTING = "administration/userManagement_table.jsp";
    public static final String ADD = "administration/addUser.jsp";


    private UserService userService;
    private long id;

    @Validate(converter = UserTypeConverter.class, on = {"viewDetails","editDetails","deleteDetails"})
    @ValidateNestedProperties({
        @Validate(field="email", converter = EmailTypeConverter.class, required = true, trim = true, on={"saveDetails","add"}),
        @Validate(field="expiryDate", converter=DateTypeConverter.class,on={"saveDetails","add"}),
        @Validate(field="username", required = true, trim = true, on={"add"}),
        @Validate(field="password", required = true, trim =true, on={"add"})
    })
    private User user;

    @Validate(required=true, trim=true, on="add")
    private String confirmPassword = "";


    @Validate(converter= EnumeratedTypeConverter.class, on="saveDetails")
    private Set<PermissionType> permissions = new HashSet<PermissionType>();

    @Validate(converter = EnumeratedTypeConverter.class, on="saveDetails")
    private Set<RoleType> roles = new HashSet<RoleType>();

    private ExtendedPaginatedList userPaginationList;



    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }


    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }


    public Set<PermissionType> getPermissions() {
        return permissions;
    }

    public void setPermissions(Set<PermissionType> permissions) {
        this.permissions = permissions;
    }

    public Set<RoleType> getRoles() {
        return roles;
    }

    public void setRoles(Set<RoleType> roles) {
        this.roles = roles;
    }

    public ExtendedPaginatedList getUserPaginationList() {
        return userPaginationList;
    }

    public void setUserPaginationList(ExtendedPaginatedList userPaginationList) {
        this.userPaginationList = userPaginationList;
    }

    public void setConfirmPassword(String confirmPassword) {
        this.confirmPassword = confirmPassword;
    }

    public String getConfirmPassword() {
        return confirmPassword;
    }


    // === view
    /**
     * Loads the user management page
     * Input - none
     * Output - userPaginationList
     * @return {@link Resolution}
     */
    @DefaultHandler
    @Profiled(tag="UserManagementActionBean_view", logFailuresSeparately = true)
    public Resolution view() {
        userPaginationList = userService.getAllUsers(getPaginatedList());
        return new ForwardResolution(generateThemePath(VIEW));
    }


    // === viewDetails
    /**
     * (Ajax) Updates portion of the page with viewable only user's details.
     * Input - user
     * Output - user
     * @return {@link net.sourceforge.stripes.action.Resolution}
     */
    @Profiled(tag="UserManagementActionBean_viewDetails", logFailuresSeparately = true)
    public Resolution viewDetails() {
        return new ForwardResolution(generateThemePath(VIEW_DETAILS));
    }

    // === editDetails
    /**
     * (Ajax) Updates portion of the page with editable user's details.
     * Input - user
     * Output - user
     * @return {@link net.sourceforge.stripes.action.Resolution}
     */
    @Profiled(tag="UserManagementActionBean_editDetails", logFailuresSeparately = true)
    public Resolution editDetails() {
        return new ForwardResolution(generateThemePath(EDIT_DETAILS));
    }


    
    // === deleteDetails
    /**
     * (Ajax) Delete user and update the user listing table portion of the page (deleted user should not appear there anymore).
     * Input - user
     * Output - userPaginationList
     * @return {@link net.sourceforge.stripes.action.Resolution}
     */
    @Profiled(tag="UserManagementActionBean_deleteDetails", logFailuresSeparately = true)
    public Resolution deleteDetails() {
        userService.deleteUser(user);
        userPaginationList = userService.getAllUsers(getPaginatedList());
        getContext().getMessages().add(new LocalizableMessage("foo.bar.blog.web.action.administration.UserManagementActionBean.deleteSuccess", user.getUsername()));
        return new ForwardResolution(generateThemePath(DELETE_DETAILS));
    }

    // === userListing
    /**
     * (Ajax) Update the user listing table portion of the page.
     * Input - none
     * Output - userPaginationList
     * @return
     */
    @Profiled(tag="UserManagementActionBean_userListing", logFailuresSeparately = true)
    public Resolution userListing() {
        userPaginationList = userService.getAllUsers(getPaginatedList());
        return new ForwardResolution(generateThemePath(USER_LISTING));
    }

    // === viewAdd
    /**
     * (Ajax) Update portions of the page with form to add a user.
     * Input - none
     * Output - user
     * @return
     */
    @Profiled(tag="UserManagementActionBean_viewAdd", logFailuresSeparately = true)
    public Resolution viewAdd() {
        user = new User();
        return new ForwardResolution(generateThemePath(ADD));
    }


    // === add
    /**
     * (Ajax) Add the user, then update the portion of the page with message and a new blank add user form.
     * Input - user, confirmPassword
     * Output - user
     * @return {@link Resolution}
     */
    @Profiled(tag="UserManagementActionBean_add", logFailuresSeparately = true)
    public Resolution add() {
        updateUserRoleAndPermission();
        userService.saveOrUpdateUser(user);
        getContext().getMessages().add(new LocalizableMessage("foo.bar.blog.web.action.administration.UserManagementActionBean.addSuccess", user.getUsername()));
        setAjaxRequestSuccessHeader();

        user = new User();
        return new ForwardResolution(generateThemePath(ADD));
    }
    @Before(stages={LifecycleStage.BindingAndValidation}, on="add")
    public void beforeAdd() {
        user = new User();    
    }
    @ValidationMethod(on={"add"})
    public void validateAdd(ValidationErrors errors) {
        if (user.getPassword() == null || confirmPassword == null || (!user.getPassword().equals(confirmPassword))) {
            errors.add("user.password", new LocalizableError("errors.passwordMismatch"));
        }
        if (userService.isUsernameExists(user.getUsername())) {
            errors.add("user.username", new LocalizableError("errors.usernameExists", user.getUsername()));
        }
    }



    // === saveDetails
    /**
     * (Ajax) save user, then update portion of the page with form (filled with user details) and message.
     * Input - user, roles, permissions
     * Output - user
     * @return {@link net.sourceforge.stripes.action.Resolution}
     */
    @Profiled(tag="UserManagementActionBean_saveDetails", logFailuresSeparately = true)
    public Resolution saveDetails() {
        updateUserRoleAndPermission();
        userService.saveOrUpdateUser(user);
        getContext().getMessages().add(new LocalizableMessage("foo.bar.blog.web.action.administration.UserManagementActionBean.saveSuccess", user.getUsername()));
        setAjaxRequestSuccessHeader();
        return new ForwardResolution(generateThemePath(EDIT_DETAILS));
    }
    @Before(stages={LifecycleStage.BindingAndValidation}, on="saveDetails")
    public void beforeSaveDetails() {
        long userId = Long.valueOf(getRequestParameter("id"));
        user = userService.getUserById(userId);
    }




    @SpringBean("userService")
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    private void updateUserRoleAndPermission() {
        // role
        List<Role> rolesNeededToBeRemoved = new ArrayList<Role>();
        for (RoleType roleType : EnumSet.allOf(RoleType.class)) {
            if (roles.contains(roleType)) { // this is selected
                if (!user.hasRole(roleType)) { // if the user doesn't have this add it
                    Role r = new Role();
                    r.setType(roleType);
                    user.getRoles().add(r);
                }
            }
            else { // this is not selected
                if (user.hasRole(roleType)) { // if user have it we need to remove it
                    rolesNeededToBeRemoved.add(user.getRole(roleType));
                    user.removeRole(roleType);
                }
            }
        }

        // permissions
        List<Permission> permissionsNeededToBeRemoved = new ArrayList<Permission>();
        for (PermissionType permissionType: EnumSet.allOf(PermissionType.class)) {
            if (permissions.contains(permissionType)) {
                if (!user.hasPermission(permissionType)) {
                    Permission p = new Permission();
                    p.setType(permissionType);
                    user.getPermissions().add(p);
                }
            }
            else {
                 if (user.hasPermission(permissionType)) {
                     permissionsNeededToBeRemoved.add(user.getPermission(permissionType));
                     user.removePermission(permissionType);
                 }
            }
        }

        // NOTE:
        // we need to do this cause it's a one-to-many relationship and the
        // managing / owning side is the many side such that we don't have
        // dangling Permission or Roles. 
        userService.deletePermission(permissionsNeededToBeRemoved);
        userService.deleteRole(rolesNeededToBeRemoved);
    }

}
